//
//  PJMToast.swift
//  PJMProFramework
//
//  Created by hui wang on 2024/11/28.
//

import UIKit

public class Toast {
    
    public static let shared = Toast()
    
    public static var toastStyle: (() -> PJMToastStyle)?
    
    init() {
        PJMToastManager.shared.style = Self.toastStyle?()
        PJMToastManager.shared.duration = 3.0
        PJMToastManager.shared.position = .bl_position
    }
    
    public static func show(text: String) {
        Toast.shared.showToast(text)
    }
    
    public static func showToast(text: String, completion: ((Bool) -> Void)? = nil) {
        DispatchQueue.main.async {
            PJMUITool.keyWindow?.pjm_makeToast(text, completion: completion)
        }
    }
    
    public func showToast(_ message: String) {
        DispatchQueue.main.async {
            PJMUITool.rootWindow?.pjm_makeToast(message, completion: { _ in })
        }
    }
    
    public func clearToast() {
        DispatchQueue.main.async {
            PJMUITool.keyWindow?.pjm_hideAllToasts()
        }
    }
    
}

public extension UIView {
    
    // MARK: - Make Toast Methods
    func pjm_makeToast(_ message: String?,
                   duration: TimeInterval = PJMToastManager.shared.duration,
                   position: PJMToastPosition = PJMToastManager.shared.position,
                   style: PJMToastStyle = PJMToastManager.shared.style ?? PJMToastStyle(),
                   completion: ((_ didTap: Bool) -> Void)? = nil) {
        do {
            let toast = try pjm_toastViewForMessage(message, style: style)
            pjm_showToast(toast, duration: duration, position: position, completion: completion)
        } catch PJMToastError.missingParameters {
            assertionFailure()
        } catch {}
    }
    
    func  pjm_makeToast(_ message: String?,
                   duration: TimeInterval = PJMToastManager.shared.duration,
                   point: CGPoint,
                   style: PJMToastStyle = PJMToastManager.shared.style!,
                   completion: ((_ didTap: Bool) -> Void)?) {
        do {
            let toast = try pjm_toastViewForMessage(message, style: style)
            pjm_showToast(toast, duration: duration, point: point, completion: completion)
        } catch PJMToastError.missingParameters {
            assertionFailure()
        } catch {}
    }

    // MARK: - Show Toast Methods
    func  pjm_showToast(_ toast: UIView,
                   duration: TimeInterval = PJMToastManager.shared.duration,
                   position: PJMToastPosition = PJMToastManager.shared.position,
                   completion: ((_ didTap: Bool) -> Void)? = nil) {
        let point = position.centerPoint(forToast: toast, inSuperview: self)
        pjm_showToast(toast, duration: duration, point: point, completion: completion)
    }

    func pjm_showToast(_ toast: UIView,
                   duration: TimeInterval = PJMToastManager.shared.duration,
                   point: CGPoint,
                   completion: ((_ didTap: Bool) -> Void)? = nil) {
        objc_setAssociatedObject(toast, &PJMToastKeys.completion, PJMToastCompletionWrapper(completion), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

        if PJMToastManager.shared.isQueueEnabled, pjm_activeToasts.count > 0 {
            objc_setAssociatedObject(toast, &PJMToastKeys.duration, NSNumber(value: duration), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            objc_setAssociatedObject(toast, &PJMToastKeys.point, NSValue(cgPoint: point), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

            pjm_queue.add(toast)
        } else {
            pjm_showToast(toast, duration: duration, point: point)
        }
    }

    // MARK: - Hide Toast Methods
    func pjm_hideToast() {
        guard let activeToast = pjm_activeToasts.firstObject as? UIView else { return }
        pjm_hideToast(activeToast)
    }
    
    func pjm_hideToast(_ toast: UIView) {
        guard pjm_activeToasts.contains(toast) else { return }
        pjm_hideToast(toast, fromTap: false)
    }
    
    func pjm_hideAllToasts(clearQueue: Bool = true) {
        if clearQueue {
            pjm_clearToastQueue()
        }
        
        pjm_activeToasts.compactMap { $0 as? UIView }.forEach { pjm_hideToast($0) }
    }
    
    func pjm_clearToastQueue() {
        pjm_queue.removeAllObjects()
    }
}

public struct PJMToastStyle {

    public init() {}

    public var backgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8)

    public var textColor: UIColor = .white

    /// 最小水平外边距
    public var minHorizontalMargin: CGFloat = 16.0

    /// 水平内边距
    public var horizontalPadding: CGFloat = 15.0

    /// 垂直内边距
    public var verticalPadding: CGFloat = 15.0

    public var cornerRadius: CGFloat = 4.0

    public var font: UIFont = .systemFont(ofSize: 12.0)

    public var textAlignment: NSTextAlignment = .center

    public var numberOfLines = 0

    /// 显示边框
    public var displayBorder = false
    public var borderWidth: CGFloat = 1
    public var borderColor: UIColor = .white

    /// 显示阴影
    public var displayShadow = false
    public var shadowColor: UIColor = .black
    public var shadowOpacity: Float = 0.8 {
        didSet {
            shadowOpacity = max(min(shadowOpacity, 1.0), 0.0)
        }
    }
    public var shadowRadius: CGFloat = 6.0
    public var shadowOffset = CGSize(width: 4.0, height: 4.0)

    /// 显示消失动画时长
    public var fadeDuration: TimeInterval = 0.3

    static func blToastStyle() -> PJMToastStyle {
        var style = PJMToastStyle()
        style.backgroundColor = .white
        return style
    }
    
    static func pjmToastStyle() -> PJMToastStyle {
        var style = PJMToastStyle()
        style.backgroundColor = .white
        return style
    }
}

public class PJMToastManager {
    
    public static let shared = PJMToastManager()
    
    /// toast UI
    public var style: PJMToastStyle? = PJMToastStyle()

    /// 点击toast使之消失
    public var isTapToDismissEnabled = true

    /// 是否启用toast队列
    /// 如果启用，toast将按顺序展示出来
    public var isQueueEnabled = false

    /// toast显示时间
    public var duration: TimeInterval = 3.0

    /// toast位置
    public var position: PJMToastPosition = .bl_position

}

public enum PJMToastPosition {
    case bl_position
    
    fileprivate func centerPoint(forToast toast: UIView, inSuperview superview: UIView) -> CGPoint {
        switch self {
        case .bl_position:
            return CGPoint(x: superview.bounds.size.width / 2.0, y: superview.bounds.size.height / 2.0)
        }
    }
}

extension UIView {

    struct PJMToastKeys {
        static var timer        = "PJM.toast-swift.timer"
        static var duration     = "PJM.toast-swift.duration"
        static var point        = "PJM.toast-swift.point"
        static var completion   = "PJM.toast-swift.completion"
        static var activeToasts = "PJM.toast-swift.activeToasts"
        static var queue        = "PJM.toast-swift.queue"
    }

    class PJMToastCompletionWrapper {
        let completion: ((Bool) -> Void)?

        init(_ completion: ((Bool) -> Void)?) {
            self.completion = completion
        }
    }

    enum PJMToastError: Error {
        case missingParameters
    }

    var pjm_activeToasts: NSMutableArray {
        get {
            if let activeToasts = objc_getAssociatedObject(self, &PJMToastKeys.activeToasts) as? NSMutableArray {
                return activeToasts
            } else {
                let activeToasts = NSMutableArray()
                objc_setAssociatedObject(self, &PJMToastKeys.activeToasts, activeToasts, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                return activeToasts
            }
        }
    }

    var pjm_queue: NSMutableArray {
        get {
            if let queue = objc_getAssociatedObject(self, &PJMToastKeys.queue) as? NSMutableArray {
                return queue
            } else {
                let queue = NSMutableArray()
                objc_setAssociatedObject(self, &PJMToastKeys.queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                return queue
            }
        }
    }

    func pjm_showToast(_ toast: UIView, duration: TimeInterval, point: CGPoint) {
        toast.center = point
        toast.alpha = 0.0

        if PJMToastManager.shared.isTapToDismissEnabled {
            let recognizer = UITapGestureRecognizer(target: self, action: #selector(UIView.tapToast(_:)))
            toast.addGestureRecognizer(recognizer)
            toast.isUserInteractionEnabled = true
            toast.isExclusiveTouch = true
        }

        pjm_activeToasts.add(toast)
        self.addSubview(toast)

        UIView.animate(withDuration: PJMToastManager.shared.style?.fadeDuration ?? 0.3, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: {
            toast.alpha = 1.0
        }) { _ in
            let timer = Timer(timeInterval: duration, target: self, selector: #selector(UIView.toastTimerFinished(_:)), userInfo: toast, repeats: false)
            RunLoop.main.add(timer, forMode: .common)
            objc_setAssociatedObject(toast, &PJMToastKeys.timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    func pjm_hideToast(_ toast: UIView, fromTap: Bool) {
        if let timer = objc_getAssociatedObject(toast, &PJMToastKeys.timer) as? Timer {
            timer.invalidate()
        }

        UIView.animate(withDuration: PJMToastManager.shared.style?.fadeDuration ?? 0.3, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: {
            toast.alpha = 0.0
        }) { _ in
            toast.removeFromSuperview()
            self.pjm_activeToasts.remove(toast)

            if let wrapper = objc_getAssociatedObject(toast, &PJMToastKeys.completion) as? PJMToastCompletionWrapper, let completion = wrapper.completion {
                if self.pjm_activeToasts.count == 0 {
                    completion(fromTap)
                }
            }

            if let nextToast = self.pjm_queue.firstObject as? UIView, let duration = objc_getAssociatedObject(nextToast, &PJMToastKeys.duration) as? NSNumber, let point = objc_getAssociatedObject(nextToast, &PJMToastKeys.point) as? NSValue {
                self.pjm_queue.removeObject(at: 0)
                self.pjm_showToast(nextToast, duration: duration.doubleValue, point: point.cgPointValue)
            }
        }
    }

    @objc private func tapToast(_ recognizer: UITapGestureRecognizer) {
        guard let toast = recognizer.view else { return }
        pjm_hideToast(toast, fromTap: true)
    }

    @objc private func toastTimerFinished(_ timer: Timer) {
        guard let toast = timer.userInfo as? UIView else { return }
        pjm_hideToast(toast)
    }

    func pjm_toastViewForMessage(_ message: String?, style: PJMToastStyle) throws -> UIView {
        guard let msg = message, !msg.isEmpty else {
            throw PJMToastError.missingParameters
        }

        let wrapperView = UIView()
        wrapperView.backgroundColor = style.backgroundColor
        wrapperView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
        wrapperView.layer.cornerRadius = style.cornerRadius

        if style.displayBorder {
            wrapperView.layer.borderWidth = style.borderWidth
            wrapperView.layer.borderColor = style.borderColor.cgColor
        }

        if style.displayShadow {
            wrapperView.layer.shadowColor = style.shadowColor.cgColor
            wrapperView.layer.shadowOpacity = style.shadowOpacity
            wrapperView.layer.shadowRadius = style.shadowRadius
            wrapperView.layer.shadowOffset = style.shadowOffset
        }

        let messageLabel = UILabel()
        messageLabel.text = msg
        messageLabel.numberOfLines = style.numberOfLines
        messageLabel.font = style.font
        messageLabel.textAlignment = style.textAlignment
        messageLabel.textColor = style.textColor
        messageLabel.lineBreakMode = .byTruncatingTail
        messageLabel.backgroundColor = .clear

        let maxMessageSize = CGSize(width: self.bounds.width - style.minHorizontalMargin * 2 - style.horizontalPadding * 2, height: self.bounds.size.height * 0.8 - style.verticalPadding * 2)
        let messageSize = messageLabel.sizeThatFits(maxMessageSize)
        let actualWidth = min(messageSize.width, maxMessageSize.width)
        let actualHeight = min(messageSize.height, maxMessageSize.height)
        
        messageLabel.frame = CGRect(x: style.horizontalPadding, y: style.verticalPadding, width: actualWidth, height: actualHeight)
        wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: actualWidth + style.horizontalPadding * 2, height: actualHeight + style.verticalPadding * 2)
        wrapperView.addSubview(messageLabel)

        return wrapperView
    }

}
